import { DI, ServicesAccessor } from './di';
import { IMain } from './main.id';
import { Descriptor } from './descriptor';

export class MainService implements IMain {
    private readonly proxy = new Map();
    constructor(private readonly service) {
        this.service.set(IMain, this);
    }

    invoke(fn: (x: ServicesAccessor) => any): any {
        const accessor: ServicesAccessor = {
            get: (id) => {
                const sd = this.createAndCacheServiceProxy(id);
                if (!sd) {
                    throw new Error(`[invokeFunction] unknown service '${id}'`);
                }
                return sd;
            },
        };
        return fn(accessor);
    }

    private createAndCacheServiceProxy(id) {
        if (this.proxy.has(id)) {
            return this.proxy.get(id);
        }

        if (!this.service.has(id)) {
            return;
        }

        const proxy_index = { id };
        const proxy = new Proxy(proxy_index as any, {
            get: (target, prop) => {
                if (prop in target) {
                    return target[prop];
                }

                if (!target.inst) {
                    this.createInstance(target);
                }

                const r = target.inst[prop];
                if (typeof r === 'function') {
                    return r.bind(target.inst);
                }
                return r;
            },
        });
        this.proxy.set(id, proxy);

        return proxy;
    }

    private createInstance(proxy) {
        const sd = this.service.get(proxy.id);
        const next_args = sd.args.slice();

        if (sd instanceof Descriptor) {
            for (const dep of DI.getDep(sd.ctor)) {
                next_args[dep.index] = this.createAndCacheServiceProxy(dep.id);
            }
        }
        proxy.inst = new sd.ctor(...next_args);
    }
}
